Repository navigation
solarcam: run ffmpeg without a shell; camera-supplied file names reached it - #15
Conversation
PR Summary by QodoPrevent shell injection in SolarCam video conversion
AI Description
Diagram
High-Level Assessment
Files changed (1)
|
Code Review by Qodo
1.
|
…hed it
convertFile() built the ffmpeg command as one f-string and ran it with
shell=True. Its paths come from generateTargetFilePath(), which takes
segments of the FileName the camera returns from OPFileQuery and only
replaces "." with "-". A camera returning
/mnt/sd/x;touch${IFS}PWNED;/001/... therefore ran commands on the
downloading host whenever target_filetype_video was set.
ffmpeg now gets an argv list, and generateTargetFilePath() keeps only
[A-Za-z0-9_-] in the name it derives. Real names
(2024-01-15_12-00-00) are unchanged, so existing blacklists and .dmp
entries still match.
convertFile() also logged success and deleted the downloaded recording
even when ffmpeg failed. It now keeps the source on failure.
Reported by @waseem-cve.
Keeping the source after a failed ffmpeg run left save_files() treating it as done, so the conversion was never retried. With conversion on, an existing source is now converted again without a second download, and a partial output from the failed run is removed so it is not mistaken for a finished conversion.
a26acb0 to
62c8e47
Compare
SolarCam.convertFile()ran ffmpeg throughshell=True. The paths it was given include parts of theFileNamethe camera returns, so a hostile or compromised camera could run commands on the machine runningdownload-local-files.py(the Docker image'sCMD). This only happens whentarget_filetype_videois set. Reported by @waseem-cve (filed as GHSA-w2h5-ww59-7hxr on OpenIPC/firmware, where this code does not live).Changes (
solarcam.pyonly)convertFile()passes ffmpeg an argv list instead of a shell string. The flags are unchanged.generateTargetFilePath()keeps only[A-Za-z0-9_-]in the name it builds from the camera's segments. This is defence in depth, and it also keeps shell and filesystem oddities out of the download dir. Real names come out unchanged (2024-01-15_12-00-00), so existing blacklists and.dmpdumps still match.convertFile()used to log "successfully converted" and delete the downloaded recording even when ffmpeg failed. It now returns early on failure and keeps the source file.Testing
I used a harness that stubs
DVRIPCam.download_fileand puts a fakeffmpegonPATH. The fake records its argv and exits with 0 or 1. Each run callssave_files(dir, [file], target_filetype=".webm")with one benign and one maliciousFileName:/mnt/sd/2024-01-15/001/12.00.00-12.05.00[R][@1a][0].h264/mnt/sd/x;touch${IFS}PWNED;/001/12.00.00-12.05.00[R][@1a][0].h264Before (master
6ea861a):After:
Before the fix, the malicious name creates
PWNEDand ffmpeg only receives.../dl/x. On rc=1 the benign recording is deleted (dl/: []). After the fix there's no marker, the malicious name is flattened tox_touch__IFS_PWNED__12-00-00, the benign path is byte-identical, and on rc=1 the source is kept. (dl/is empty on rc=0 because the fake ffmpeg writes no output and the source is then removed, as intended.)python3 -m py_compile solarcam.pypasses. Not run against a real solar camera or a real ffmpeg.